﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AIProject.Utility;

namespace AIProject.Pylos
{
    class State
    {
        private RandomNumbers rn;
        private Piece[,] board;
        private Boolean whiteToMove;
        private Boolean whiteWon;
        private Boolean blackWon;
        private Boolean tie;
        private int whiteReservePieces;
        private int blackReservePieces;
        private int hashCode;
        private int moves;

        public Piece[,] Pieces
        {
            get
            {
                return this.board;
            }
            set
            {
            }
        }

        public Boolean WhiteToMove
        {
            get
            {
                return this.whiteToMove;
            }
            set
            {
            }
        }

        public Boolean WhiteWon
        {
            get
            {
                return this.whiteWon;
            }
            set
            {
            }
        }

        public Boolean BlackWon
        {
            get
            {
                return this.blackWon;
            }
            set
            {
            }
        }

        public Boolean Tie
        {
            get
            {
                return this.tie;
            }
            set
            {
            }
        }

        public int WhiteReservePieces
        {
            get
            {
                return this.whiteReservePieces;
            }
            set
            {
            }
        }

        public int BlackReservePieces
        {
            get
            {
                return this.blackReservePieces;
            }
            set
            {
            }
        }

        public int NumMoves
        {
            get
            {
                return this.moves;
            }
            set
            {
            }
        }

        public State()
        {
            this.board = new Piece[4, 10];
            this.rn = new RandomNumbers();
            this.whiteToMove = true;

            this.blackReservePieces = 15;
            this.whiteReservePieces = 15;

            generateHashCode();
        }

        private State(Piece[,] board, RandomNumbers rn, Boolean whiteWon, Boolean blackWon, Boolean whiteToMove, int whiteReservePieces, int blackReservePieces, int hashCode, int moves)
        {
            this.board = board;
            this.rn = rn;
            this.whiteWon = whiteWon;
            this.blackWon = blackWon;
            this.whiteToMove = whiteToMove;
            this.whiteReservePieces = whiteReservePieces;
            this.blackReservePieces = blackReservePieces;
            this.hashCode = hashCode;
            this.moves = moves;
            this.tie = false;
        }

        public void print()
        {
            String toWrite = "";
            Console.WriteLine("\n\n\n");
            Console.WriteLine((whiteToMove ? "White to Move" : "Black to Move"));
            Console.WriteLine("White Pieces: " + whiteReservePieces + "\t\t\tTotal moves: " + moves);
            Console.WriteLine("Black Pieces: " + blackReservePieces);
            Console.WriteLine("_________\t\t_______\t\t_____\t\t___");

            for (int i = 0; i < 4; i++)
            {
                toWrite = "|";

                for (int j = 0; j < 4; j++)
                {
                    if (board[i, j] != null)
                    {
                        toWrite += board[i, j].ToString() + (board[i, j].Moveable ? "T" : "F");
                        toWrite += "|";
                    }
                    else
                    {
                        toWrite += " |";
                    }
                }

                if (i < 3)
                {
                    toWrite += "\t\t|";

                    for (int j = 4; j < 7; j++)
                    {
                        if (board[i, j] != null)
                        {
                            toWrite += board[i, j].ToString() + (board[i, j].Moveable ? "T" : "F");
                            toWrite += "|";
                        }
                        else
                        {
                            toWrite += " |";
                        }


                    }
                }

                if (i < 2)
                {
                    toWrite += "\t\t|";

                    for (int j = 7; j < 9; j++)
                    {
                        if (board[i, j] != null)
                        {
                            toWrite += board[i, j].ToString() + (board[i, j].Moveable ? "T" : "F");
                            toWrite += "|";
                        }
                        else
                        {
                            toWrite += " |";
                        }
                    }
                }

                if (i == 0)
                {
                    toWrite += "\t\t|";
                    if (board[0, 9] != null)
                    {
                        toWrite += board[0, 9].ToString() + (board[0, 9].Moveable ? "T" : "F");
                        toWrite += "|";
                    }
                    else
                    {
                        toWrite += " |";
                    }
                }
                Console.WriteLine(toWrite);

                if (i == 0)
                {
                    Console.WriteLine("_________\t\t_______\t\t_____\t\t___");
                }
                else if (i < 2)
                {
                    Console.WriteLine("_________\t\t_______\t\t_____");
                }
                else if (i < 3)
                {
                    Console.WriteLine("_________\t\t_______");

                }
                else
                {
                    Console.WriteLine("_________");
                }
            }

            if (whiteWon)
            {
                Console.Out.WriteLine("White won");
            }
            else if (blackWon)
            {
                Console.Out.WriteLine("Black won");
            }
            else if (Tie)
            {
                Console.Out.WriteLine("Move limit reached. Game Tied");
            }
        }

        public List<Action> getActions()
        {
            List<Action> toReturn = new List<Action>();
            List<Piece> moveablePieces = getMoveable();

            for (int i = 0; i < 10; i++)
            {
                for (int j = 0; j < (i < 4 ? 4 : (i < 7 ? 3 : 2)); j++)
                {
                    Action[] tempActions = new Action[50];
                    Piece[] lowerPieces = getLowerPieces(i, j);
                    int actionIndex = 0;

                    if (isLegal(j, i))
                    {
                        Move[] moves = new Move[3];
                        moves[0] = new Move(-1, -1, i, j);
                        tempActions[actionIndex] = new Action(moves);
                        actionIndex++;

                        if (moveablePieces.Count != 0)
                        {
                            foreach (Piece piece in moveablePieces)
                            {
                                if (i > 3 && i < 7 && piece.Location.X < 4)
                                {
                                    if (!piece.Equals(lowerPieces[0]) && !piece.Equals(lowerPieces[1]) && !piece.Equals(lowerPieces[2]) && !piece.Equals(lowerPieces[3]))
                                    {
                                        moves = new Move[3];
                                        moves[0] = new Move(piece.Location.X, piece.Location.Y, i, j);
                                        tempActions[actionIndex] = new Action(moves);
                                        actionIndex++;
                                    }
                                }
                                else if (i > 6 && i < 9 && piece.Location.X < 7)
                                {
                                    if (!piece.Equals(lowerPieces[0]) && !piece.Equals(lowerPieces[1]) && !piece.Equals(lowerPieces[2]) && !piece.Equals(lowerPieces[3]))
                                    {
                                        moves = new Move[3];
                                        moves[0] = new Move(piece.Location.X, piece.Location.Y, i, j);
                                        tempActions[actionIndex] = new Action(moves);
                                        actionIndex++;
                                    }
                                }
                            }
                        }

                        for (int k = 0; k < actionIndex; k++)
                        {
                            if (makesRemovalSquares(j, i) || makesRemovalLines(j, i))
                            {
                                moves = new Move[3];
                                moves[0] = tempActions[k][0].clone();
                                moves[1] = new Move(i, j, -1, -1);

                                Action startAction = new Action(moves);
                                toReturn.Add(startAction);

                                foreach (Piece piece in moveablePieces)
                                {
                                    moves = new Move[3];
                                    moves[0] = startAction[0].clone();
                                    moves[1] = startAction[1].clone();
                                    moves[2] = new Move(piece.Location.X, piece.Location.Y, -1, -1);

                                    if (moves[2].Source.X != moves[0].Source.X && moves[2].Source.Y != moves[0].Source.Y)
                                    {
                                        if (!piece.Equals(lowerPieces[0]) && !piece.Equals(lowerPieces[1]) && !piece.Equals(lowerPieces[2]) && !piece.Equals(lowerPieces[3]))
                                        {
                                            toReturn.Add(new Action(moves));
                                        }
                                    }
                                }

                                for (int l = 0; l < moveablePieces.Count; l++)
                                {
                                    moves = new Move[3];
                                    moves[0] = tempActions[k][0].clone();
                                    moves[1] = new Move(moveablePieces[l].Location.X, moveablePieces[l].Location.Y, -1, -1);
                                    startAction = new Action(moves);

                                    if (moves[1].Source.X != moves[0].Source.X && moves[1].Source.Y != moves[0].Source.Y)
                                    {
                                        if (!moveablePieces[l].Equals(lowerPieces[0]) && !moveablePieces[l].Equals(lowerPieces[1]) && !moveablePieces[l].Equals(lowerPieces[2]) && !moveablePieces[l].Equals(lowerPieces[3]))
                                        {
                                            toReturn.Add(startAction);

                                            for (int m = (l + 1); m < moveablePieces.Count; m++)
                                            {
                                                moves = new Move[3];
                                                moves[0] = startAction[0].clone();
                                                moves[1] = startAction[1].clone();
                                                moves[2] = new Move(moveablePieces[m].Location.X, moveablePieces[m].Location.Y, -1, -1);

                                                if (moves[2].Source.X != moves[0].Source.X && moves[2].Source.Y != moves[0].Source.Y && moves[2].Source.X != moves[1].Source.X && moves[2].Source.Y != moves[1].Source.Y)
                                                {
                                                    if (!moveablePieces[m].Equals(lowerPieces[0]) && !moveablePieces[m].Equals(lowerPieces[1]) && !moveablePieces[m].Equals(lowerPieces[2]) && !moveablePieces[m].Equals(lowerPieces[3]))
                                                    {
                                                        toReturn.Add(new Action(moves));
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            else
                            {
                                toReturn.Add(tempActions[k]);
                            }
                        }
                    }
                }
            }
            toReturn.Reverse();
            return toReturn;
        }

        public void update(Action a)
        {
            for (int i = 0; i < 3; i++)
            {
                if (a[i] != null)
                {
                    Point src = a[i].Source; //x = y = -1 if placing a piece from our reserve
                    Point dest = a[i].Destination; //x = y = -1 if removing a piece from the board

                    if (src.X == -1 && src.Y == -1)
                    {
                        board[dest.Y, dest.X] = new Piece(whiteToMove, dest.X, dest.Y);

                        if (whiteToMove)
                        {
                            hashCode ^= rn[1, whiteReservePieces]; //remove existing pieces
                            whiteReservePieces--;
                            hashCode ^= rn[3, dest.Y, dest.X]; //remove no pieces at destination
                            hashCode ^= rn[1, dest.Y, dest.X]; //add a piece at destination
                            hashCode ^= rn[1, whiteReservePieces]; //add new existing pieces
                        }
                        else
                        {
                            hashCode ^= rn[2, blackReservePieces];
                            blackReservePieces--;
                            hashCode ^= rn[3, dest.Y, dest.X];
                            hashCode ^= rn[2, dest.Y, dest.X];
                            hashCode ^= rn[2, blackReservePieces];
                        }

                        if (dest.X == 9 && dest.Y == 0)
                        {
                            whiteWon = whiteToMove;
                            blackWon = !whiteToMove;
                        }

                        updateMoveability(src, dest, 1);
                    }
                    else if (dest.X == -1 && dest.Y == -1)
                    {
                        if (board[src.Y, src.X] == null)
                        {
                            throw new InvalidOperationException("Cannot remove a null piece");
                        }

                        board[src.Y, src.X] = null;

                        if (whiteToMove)
                        {
                            hashCode ^= rn[1, whiteReservePieces];
                            whiteReservePieces++;
                            hashCode ^= rn[1, src.Y, src.X]; //remove the piece at source
                            hashCode ^= rn[3, src.Y, src.X]; //add an empty at source
                            hashCode ^= rn[1, whiteReservePieces];
                        }
                        else
                        {
                            hashCode ^= rn[2, blackReservePieces];
                            blackReservePieces++;
                            hashCode ^= rn[2, src.Y, src.X];
                            hashCode ^= rn[3, src.Y, src.X];
                            hashCode ^= rn[2, blackReservePieces];
                        }

                        updateMoveability(src, dest, 2);
                    }
                    else
                    {
                        if (board[src.Y, src.X] == null)
                        {
                            throw new InvalidOperationException("Cannot promote an empty piece");
                        }

                        board[dest.Y, dest.X] = board[src.Y, src.X];
                        board[dest.Y, dest.X].Location.X = dest.X;
                        board[dest.Y, dest.X].Location.Y = dest.Y;

                        board[src.Y, src.X] = null;

                        if (whiteToMove)
                        {
                            hashCode ^= rn[1, src.Y, src.X]; //remove the piece at source
                            hashCode ^= rn[3, src.Y, src.X]; //add an empty at source

                            hashCode ^= rn[3, dest.Y, dest.X]; //remove empty at destination
                            hashCode ^= rn[1, dest.Y, dest.X]; //add a piece at destination
                        }
                        else
                        {
                            hashCode ^= rn[2, src.Y, src.X];
                            hashCode ^= rn[3, src.Y, src.X];

                            hashCode ^= rn[3, dest.Y, dest.X];
                            hashCode ^= rn[2, dest.Y, dest.X];
                        }

                        updateMoveability(src, dest, 3);
                    }
                }
                else
                {
                    break;
                }
            }

            hashCode ^= rn.WhiteToMove;
            hashCode ^= rn.BlackToMove;

            if (whiteReservePieces == 0 && !whiteWon)
            {
                blackWon = true;
            }
            else if (blackReservePieces == 0 && !blackWon)
            {
                whiteWon = true;
            }

            if (this.moves == 250)
            {
                tie = true;
            }

            this.moves++;

            whiteToMove = !whiteToMove;
        }

        public Object clone()
        {
            Piece[,] b = new Piece[4, 10];

            for (int i = 0; i < 4; i++)
            {
                for (int j = 0; j < 10; j++)
                {
                    if (board[i, j] != null)
                    {
                        b[i, j] = (Piece)board[i, j].clone();
                    }
                    else
                    {
                        b[i, j] = null;
                    }
                }
            }

            return new State(b, rn, whiteWon, blackWon, whiteToMove, whiteReservePieces, blackReservePieces, hashCode, moves);
        }

        public override int GetHashCode()
        {
            return this.hashCode;
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            State s = obj as State;

            if (s == null)
            {
                return false;
            }
            
            return s.GetHashCode() == this.GetHashCode();
        }

        private void updateMoveability(Point src, Point dest, int code) //1 means placed a piece on from reserve, 2 means removed a piece, 3 means promoted a piece to a higher or equal level
        {
            if (code == 1)
            {
                Piece[] lowerPieces = getLowerPieces(dest.X, dest.Y);

                for (int k = 0; k < 4; k++)
                {
                    if (lowerPieces[k] != null)
                    {
                        lowerPieces[k].Supporting++;
                    }
                }
            }
            else if (code == 2)
            {
                Piece[] lowerPieces = getLowerPieces(src.X, src.Y);

                for (int k = 0; k < 4; k++)
                {
                    if (lowerPieces[k] != null)
                    {
                        lowerPieces[k].Supporting--;
                    }
                }
            }
            else
            {
                Piece[] lowerPiecesSource = getLowerPieces(src.X, src.Y);
                Piece[] lowerPiecesDest = getLowerPieces(dest.X, dest.Y);

                for (int k = 0; k < 4; k++)
                {
                    if (lowerPiecesSource[k] != null)
                    {
                        lowerPiecesSource[k].Supporting--;
                    }

                    if (lowerPiecesDest[k] != null)
                    {
                        lowerPiecesDest[k].Supporting++;
                    }
                }
            }
        }

        private Boolean isLegal(int j, int i)
        {
            Piece[] lowerPieces = getLowerPieces(i, j);

            if (board[j, i] == null)
            {
                if (i < 4)
                {
                    return true;
                }

                for (int k = 0; k < 4; k++)
                {
                    if (lowerPieces[k] == null)
                    {
                        return false;
                    }
                }
                return true;
            }
            else
            {
                return false;
            }
        }

        private void generateHashCode()
        {
            hashCode = 0;
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < ((i < 4) ? 4 : ((i < 7) ? 3 : 2)); j++)
                {
                    hashCode ^= rn[3, j, i];
                }
            }

            hashCode ^= rn[3, 0, 9];
            hashCode ^= rn[1, 15];
            hashCode ^= rn[2, 15];
            hashCode ^= rn.WhiteToMove;
        }

        private List<Piece> getMoveable()
        {
            List<Piece> ret = new List<Piece>();

            for (int i = 0; i < 10; i++)
            {
                for (int j = 0; j < ((i < 4) ? 4 : (i < 3) ? 3 : 2); j++)
                {
                    if (board[j, i] != null && (board[j, i].OwnedByWhite == whiteToMove) && board[j, i].Moveable)
                    {
                        ret.Add(board[j, i]);
                    }
                }
            }
            return ret;
        }

        private Boolean makesRemovalLines(int j, int i)
        {
            int counter = 0;

            if (i < 4)
            {
                for (int x = 0; x < 4; x++)
                {
                    if (board[j, x] != null && (board[j, x].OwnedByWhite == whiteToMove))
                    {
                        counter++;
                    }
                }

                if (counter == 3)
                {
                    return true;
                }

                counter = 0;

                for (int y = 0; y < 4; y++)
                {
                    if (board[y, i] != null && (board[y, i].OwnedByWhite == whiteToMove))
                    {
                        counter++;
                    }
                }

                if (counter == 3)
                {
                    return true;
                }

                return false;
            }
            else if (i < 7)
            {
                for (int x = 4; x < 7; x++)
                {
                    if (board[j, x] != null && (board[j, x].OwnedByWhite == whiteToMove))
                    {
                        counter++;
                    }
                }

                if (counter == 2)
                {
                    return true;
                }

                counter = 0;

                for (int y = 0; y < 3; y++)
                {
                    if (board[y, i] != null && (board[y, i].OwnedByWhite == whiteToMove))
                    {
                        counter++;
                    }
                }

                if (counter == 2)
                {
                    return true;
                }

                return false;
            }
            else
            {
                return false;
            }
        }

        private Boolean makesRemovalSquares(int y, int x)
        {
            if (x > 3)
            {
                return false;
            }

            Point p1 = new Point(x - 1, y - 1);
            Point p2 = new Point(x, y - 1);
            Point p3 = new Point(x - 1, y);

            int counter = 0;

            if (p1.X >= 0 && p1.Y >= 0 && p2.X >= 0 && p2.Y >= 0 && p3.X >= 0 && p3.Y >= 0)
            {
                counter += (board[p1.Y, p1.X] != null && (board[p1.Y, p1.X].OwnedByWhite == whiteToMove) ? 1 : 0);
                counter += (board[p2.Y, p2.X] != null && (board[p2.Y, p2.X].OwnedByWhite == whiteToMove) ? 1 : 0);
                counter += (board[p3.Y, p3.X] != null && (board[p3.Y, p3.X].OwnedByWhite == whiteToMove) ? 1 : 0);
            }

            if (counter == 3)
            {
                return true;
            }

            p1 = new Point(x, y - 1);
            p2 = new Point(x + 1, y - 1);
            p3 = new Point(x + 1, y);

            counter = 0;

            if (p1.X >= 0 && p1.X < 4 && p1.Y >= 0 && p1.Y < 4 && p2.X >= 0 && p2.X < 4 && p2.Y >= 0 && p2.Y < 4 && p3.X >= 0 && p3.X < 4 && p3.Y >= 0 && p3.Y < 4)
            {
                counter += (board[p1.Y, p1.X] != null && (board[p1.Y, p1.X].OwnedByWhite == whiteToMove) ? 1 : 0);
                counter += (board[p2.Y, p2.X] != null && (board[p2.Y, p2.X].OwnedByWhite == whiteToMove) ? 1 : 0);
                counter += (board[p3.Y, p3.X] != null && (board[p3.Y, p3.X].OwnedByWhite == whiteToMove) ? 1 : 0);
            }

            if (counter == 3)
            {
                return true;
            }

            p1 = new Point(x + 1, y);
            p2 = new Point(x + 1, y + 1);
            p3 = new Point(x, y + 1);
          
            counter = 0;

            if (p1.X >= 0 && p1.X < 4 && p1.Y >= 0 && p1.Y < 4 && p2.X >= 0 && p2.X < 4 && p2.Y >= 0 && p2.Y < 4 && p3.X >= 0 && p3.X < 4 && p3.Y >= 0 && p3.Y < 4)
            {
                counter += (board[p1.Y, p1.X] != null && (board[p1.Y, p1.X].OwnedByWhite == whiteToMove) ? 1 : 0);
                counter += (board[p2.Y, p2.X] != null && (board[p2.Y, p2.X].OwnedByWhite == whiteToMove) ? 1 : 0);
                counter += (board[p3.Y, p3.X] != null && (board[p3.Y, p3.X].OwnedByWhite == whiteToMove) ? 1 : 0);
            }

            if (counter == 3)
            {
                return true;
            }

            p1 = new Point(x, y + 1);
            p2 = new Point(x - 1, y + 1);
            p3 = new Point(x - 1, y);
            
            counter = 0;

            if (p1.X >= 0 && p1.X < 4 && p1.Y >= 0 && p1.Y < 4 && p2.X >= 0 && p2.X < 4 && p2.Y >= 0 && p2.Y < 4 && p3.X >= 0 && p3.X < 4 && p3.Y >= 0 && p3.Y < 4)
            {
                counter += (board[p1.Y, p1.X] != null && (board[p1.Y, p1.X].OwnedByWhite == whiteToMove) ? 1 : 0);
                counter += (board[p2.Y, p2.X] != null && (board[p2.Y, p2.X].OwnedByWhite == whiteToMove) ? 1 : 0);
                counter += (board[p3.Y, p3.X] != null && (board[p3.Y, p3.X].OwnedByWhite == whiteToMove) ? 1 : 0);
            }

            if (counter == 3)
            {
                return true;
            }

            return false;
        }

        private Piece[] getLowerPieces(int i, int j)
        {
            Piece[] toReturn = new Piece[4];

            if (i < 4)
            {
                return toReturn;
            }
            else if (i < 7)
            {
                toReturn[0] = board[j, (i - 4)];
                toReturn[1] = board[j, (i - 3)];
                toReturn[2] = board[(j + 1), (i - 4)];
                toReturn[3] = board[(j + 1), (i - 3)];
            }
            else if (i < 9)
            {
                toReturn[0] = board[j, (i - 3)];
                toReturn[1] = board[j, (i - 2)];
                toReturn[2] = board[(j + 1), (i - 3)];
                toReturn[3] = board[(j + 1), (i - 2)];
            }
            else if (i == 9)
            {
                toReturn[0] = board[j, (i - 2)];
                toReturn[1] = board[j, (i - 1)];
                toReturn[2] = board[(j + 1), (i - 2)];
                toReturn[3] = board[(j + 1), (i - 1)];
            }

            return toReturn;
        }
    }
}
